/* Copyright (C) 1999-2004 by Peter Eastman

   This program is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT ANY 
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
   PARTICULAR PURPOSE.  See the GNU General Public License for more details. */

package artofillusion;

import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.view.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.*;
import java.util.*;

/** The ObjectViewer class is the abstract superclass of components which display 
   a single object and allow the user to edit it. */

public abstract class ObjectViewer extends ViewerCanvas
{
  protected MeshEditController controller;
  protected boolean showScene, useWorldCoords, freehandSelection, draggingBox, squareBox, sentClick;
  protected Point clickPoint, dragPoint;
  protected Vector selectBoundsPoints;
  protected Shape selectBounds;
  protected ObjectInfo thisObjectInScene;
  protected Scene theScene;
  
  protected static boolean lastUseWorldCoords;
  protected static boolean lastShowGrid;
  protected static boolean lastSnapToGrid;
  protected static boolean lastFreehand;
  protected static int lastGridSubdivisions = 10;
  protected static double lastGridSpacing = 1.0;
  
  static final RGBColor surfaceRGB = new RGBColor(0.8f, 0.8f, 1.0f);
  static final Color surfaceColor = new Color(0.8f, 0.8f, 1.0f);
  
  public ObjectViewer(MeshEditController controller, RowContainer p)
  {
    this.controller = controller;
    buildChoices(p);
    setUseWorldCoords(lastUseWorldCoords);
    setGrid(lastGridSpacing, lastGridSubdivisions, lastShowGrid, lastSnapToGrid);
    setFreehandSelection(lastFreehand);
  }
  
  /** Get the controller which maintains the state for this viewer. */
  
  public MeshEditController getController()
  {
    return controller;
  }

  public synchronized void updateImage()
  {
    Rectangle dim = getBounds();

    adjustCamera(perspectiveChoice.getSelectedIndex() == 0);
    super.updateImage();
    if (theImage == null || controller.getObject() == null)
      return;
    
    // Draw the rest of the objects in the scene.
    
    if (gr != null)
      gr.setColor(Color.black);
    if (showScene && theScene != null)
    {
      for (int i = 0; i < theScene.getNumObjects(); i++)
      {
        ObjectInfo obj = theScene.getObject(i);
        drawSceneObject(obj, obj.coords);
      }
    }

    // Draw the object being edited.

    theCamera.setObjectTransform(getDisplayCoordinates().fromLocal());
    drawObject(gr);

    // Finish up.

    drawBorder();
    if (showAxes)
      drawCoordinateAxes();
  }
  
  /** Draw one object in the scene. */
  
  private void drawSceneObject(ObjectInfo obj, CoordinateSystem coords)
  {
    if (obj == thisObjectInScene || !obj.visible)
      return;
    if (obj.object instanceof ObjectCollection)
    {
      Enumeration enm = ((ObjectCollection) obj.object).getObjects(obj, true, theScene);
      while (enm.hasMoreElements())
      {
        ObjectInfo elem = (ObjectInfo) enm.nextElement();
        CoordinateSystem elemCoords = elem.coords.duplicate();
        elemCoords.transformCoordinates(coords.fromLocal());
        drawSceneObject(elem, elemCoords);
      }
      return;
    }
    if (useWorldCoords)
      theCamera.setObjectTransform(coords.fromLocal());
    else
      theCamera.setObjectTransform(thisObjectInScene.coords.toLocal().times(coords.fromLocal()));
    if (theCamera.visibility(obj.getBounds()) == Camera.NOT_VISIBLE)
      return;
    if (renderMode == RENDER_WIREFRAME)
      Object3D.draw(gr, theCamera, obj.getWireframePreview(), obj.getBounds());
    else
    {
      RenderingMesh mesh = obj.getPreviewMesh();
      if (mesh == null)
        renderWireframe(obj.getWireframePreview(), theCamera);
      else if (renderMode == RENDER_TRANSPARENT)
        renderMeshTransparent(mesh, new ConstantVertexShader(surfaceRGB), theCamera, theCamera.getViewToWorld().timesDirection(Vec3.vz()), null);
      else
      {
        Vec3 viewDir = theCamera.getViewToWorld().timesDirection(Vec3.vz());
        VertexShader shader;
        if (renderMode == RENDER_FLAT)
          shader = new FlatVertexShader(mesh, surfaceRGB, viewDir);
        else if (renderMode == RENDER_SMOOTH)
          shader = new SmoothVertexShader(mesh, surfaceRGB, viewDir);
        else
          shader = new TexturedVertexShader(mesh, obj.object, 0.0, viewDir).optimize();
        renderMesh(mesh, shader, theCamera, obj.object.isClosed(), null);
      }
    }
  }
  
  protected abstract void drawObject(Graphics g);

  /** Get the coordinate system in which the object is displayed.  This will
      vary depending on whether the user has selected Local or Scene coordinates. */
  
  public CoordinateSystem getDisplayCoordinates()
  {
    if (useWorldCoords && thisObjectInScene != null)
      return thisObjectInScene.coords;
    else
      return controller.getObject().coords;
  }
  
  /** Get whether freehand selection mode is currently in use. */
  
  public boolean getFreehandSelection()
  {
    return freehandSelection;
  }

  /** Set whether to use freehand selection mode. */
  
  public void setFreehandSelection(boolean freehand)
  {
    freehandSelection = freehand;
    lastFreehand = freehand;
  }

  /** Get the scene this object is part of, or null if there is none. */
  
  public Scene getScene()
  {
    return theScene;
  }
  
  /** Set the scene this object is part of. */
  
  public void setScene(Scene sc, ObjectInfo thisObject)
  {
    theScene = sc;
    thisObjectInScene = thisObject;
  }

  /** Get whether the entire scene is visible. */
  
  public boolean getSceneVisible()
  {
    return showScene;
  }

  /** Set whether the entire scene is visible. */
  
  public void setSceneVisible(boolean visible)
  {
    showScene = visible;
  }

  /** Get whether to use world coordinates. */
  
  public boolean getUseWorldCoords()
  {
    return useWorldCoords;
  }

  /** Set whether to use world coordinates. */
  
  public void setUseWorldCoords(boolean use)
  {
    lastUseWorldCoords = useWorldCoords = use;
  }

  /** Set the grid parameters for this view.  This is overridden to remember
      parameters between windows. */

  public void setGrid(double spacing, int subdivisions, boolean show, boolean snap)
  {
    super.setGrid(spacing, subdivisions, show, snap);
    lastGridSpacing = spacing;
    lastGridSubdivisions = subdivisions;
    lastShowGrid = show;
    lastSnapToGrid = snap;
  }
  
  /** Begin dragging a selection region.  The variable square determines whether
      the region should be constrained to be square. */
  
  public void beginDraggingSelection(Point p, boolean square)
  {
    draggingBox = true;
    clickPoint = p;
    squareBox = square;
    dragPoint = null;
    if (freehandSelection)
      selectBoundsPoints = new Vector();
  }
  
  /** Finish dragging a selection region. */
  
  public void endDraggingSelection()
  {
    if (!draggingBox || dragPoint == null)
    {
      selectBounds = null;
      return;
    }
    repaint();
  
    // Construct the selection region.
    
    if (freehandSelection)
    {
      int n = selectBoundsPoints.size(), x[] = new int [n], y[] = new int [n];
      for (int i = 0; i < n; i++)
      {
        Point p = (Point) selectBoundsPoints.elementAt(i);
        x[i] = p.x;
        y[i] = p.y;
      }
      selectBounds = new Polygon(x, y, n);
    }
    else
      selectBounds = new Rectangle(Math.min(clickPoint.x, dragPoint.x), Math.min(clickPoint.y, dragPoint.y), 
		Math.abs(dragPoint.x-clickPoint.x), Math.abs(dragPoint.y-clickPoint.y));
  }

  /** Determine whether the selection region contains the specified point. */
  
  public boolean selectionRegionContains(Point p)
  {
    if (selectBounds instanceof Rectangle)
      return ((Rectangle) selectBounds).contains(p);
    if (selectBounds instanceof Polygon)
      return ((Polygon) selectBounds).contains(p);
    return false;
  }

  protected void mouseDragged(WidgetMouseEvent e)
  {
    Graphics g = getComponent().getGraphics();
    
    moveToGrid(e);
    if (draggingBox && freehandSelection)
    {
      // Add this point to the region boundary and draw a line.
      
      Point prev = (dragPoint == null ? clickPoint : dragPoint);
      dragPoint = e.getPoint();
      g.setColor(Color.black);
      g.drawLine(prev.x, prev.y, dragPoint.x, dragPoint.y);
      selectBoundsPoints.addElement(dragPoint);
    }
    else if (draggingBox)
    {
      // We are dragging a box, so erase and redraw it.

      g.setXORMode(Color.white);
      g.setColor(Color.black);
      if (dragPoint != null)
        g.drawRect(Math.min(clickPoint.x, dragPoint.x), Math.min(clickPoint.y, dragPoint.y), 
              Math.abs(dragPoint.x-clickPoint.x), Math.abs(dragPoint.y-clickPoint.y));
      dragPoint = e.getPoint();
      if (squareBox)
      {
        if (Math.abs(dragPoint.x-clickPoint.x) > Math.abs(dragPoint.y-clickPoint.y))
        {
          if (dragPoint.y < clickPoint.y)
            dragPoint.y = clickPoint.y - Math.abs(dragPoint.x-clickPoint.x);
          else
            dragPoint.y = clickPoint.y + Math.abs(dragPoint.x-clickPoint.x);
        }
        else
        {
          if (dragPoint.x < clickPoint.x)
            dragPoint.x = clickPoint.x - Math.abs(dragPoint.y-clickPoint.y);
          else
            dragPoint.x = clickPoint.x + Math.abs(dragPoint.y-clickPoint.y);
        }
      }
      g.drawRect(Math.min(clickPoint.x, dragPoint.x), Math.min(clickPoint.y, dragPoint.y), 
              Math.abs(dragPoint.x-clickPoint.x), Math.abs(dragPoint.y-clickPoint.y));
    }
    g.dispose();

    // Send the event to the current tool, if appropriate.

    if (sentClick)
      activeTool.mouseDragged(e, this);
  }
  
  public void previewObject()
  {
    Scene sc = new Scene();
    double dist = theCamera.getDistToScreen();
    Renderer rend = ModellingApp.getPreferences().getObjectPreviewRenderer();

    if (rend == null)
      return;
    sc.addObject(new DirectionalLight(new RGBColor(1.0f, 1.0f, 1.0f), 0.8f), theCamera.getCameraCoordinates(), "", null);
    sc.addObject(getController().getObject(), null);
    adjustCamera(true);
    rend.configurePreview();
    ObjectInfo cameraInfo = new ObjectInfo(new SceneCamera(), theCamera.getCameraCoordinates(), "");
    new RenderingDialog(UIUtilities.findFrame(this), rend, sc, theCamera, cameraInfo);
    adjustCamera(perspectiveChoice.getSelectedIndex() == 0);
  }
}